Debugger gdb
gdb
è un debugger a linea di comando che ci permette di eseguire un programma passo passo, seguendo lo stato del processore e della memoria.
Il concetto fondamentale per un debugger è quello di breakpoint, ossia un punto del codice dove l'esecuzione dovra fermarsi. I breakpoints ci permettono di eseguire rapidamente le parti del programma che non sono di interesse e fermarsi ad osservare solo le parti che ci interessano.
Quella che segue è comunque una presentazione sintetica e semplificata. Per altre opzioni e funzionalità del debugger, vedere la documentazione ufficiale o il comando
help
.
Controllo dell'esecuzione
Per istruzione corrente si intende la prossima da eseguire. Quando il debugger si ferma ad un'istruzione, si ferma prima di eseguirla.
Nome completo | Nome scorciatoia | Formato | Comportamento |
---|---|---|---|
frame | f | f | Mostra l'istruzione corrente. |
list | l | l | Mostra il sorgente attorno all'istruzione corrente. |
break | b | b label | Imposta un breakpoint alla prima istruzione dopo label. |
continue | c | c | Prosegue l'esecuzione del programma fino al prossimo breakpoint. |
step | s | s | Esegue l'istruzione corrente, fermandosi immediatamente dopo. Se l'istruzione corrente è una call , l'esecuzione si fermerà alla prima istruzione del sottoprogramma chiamato. |
next | n | n | Esegue l'istruzione corrente, fermandosi all'istruzione successiva del sottoprogramma corrente. Se l'istruzione corrente è una call , l'esecuzione si fermerà dopo il ret di del sottoprogramma chiamato. Nota: aggiungere una nop dopo ogni call prima di una nuova label. |
finish | fin | fin | Continua l'esecuzione fino all'uscita dal sottoprogramma corrente (ret ). L'esecuzione si fermerà alla prima istruzione dopo la call . |
run | r | r | Avvia (o riavvia) l'esecuzione del programma. Chiede conferma. |
quit | q | q | Esce dal debugger. Chiede conferma. |
I seguenti comandi sono definiti ad-hoc nell'ambiente del corso, e non sono quindi tipici comandi di gdb
.
Nome completo | Nome scorciatoia | Formato | Comportamento |
---|---|---|---|
rrun | rr | rr | Avvia (o riavvia) l'esecuzione del programma, senza chiedere conferma. |
qquit | Esce dal debugger, senza chiedere conferma. |
Problemi con next
Si possono talvolta incontrare problemi con il comportamento di next
, che derivano da come questa è definita e implementata.
Il comando next
distingue i frame come le sequenze di istruzioni che vanno da una label alla successiva.
Il suo comportamento è, in realtà, di continuare l'esecuzione finché non incontra di nuovo una nuova istruzione nello stesso frame di partenza.
Questa logica può essere facilmente rotta con del codice come il seguente, dove non esiste una istruzione di punto_1
che viene incontrata dopo la call
.
Quel che ne consegue è che il comando next
si comporta come continue
.
punto_1:
...
call newline
punto_2:
...
Per ovviare a questo problema, è una buona abitudine quella di aggiungere una nop
dopo ciascuna call
.
Tale nop
, appartenendo allo stesso frame punto_1
, farà regolarmente sospendere l'esecuzione.
punto_1:
...
call newline
nop
punto_2:
...
Ispezione dei registri
Nome completo | Nome scorciatoia | Formato | Comportamento |
---|---|---|---|
info registers | i r | i r | Mostra lo stato di (quasi) tutti i registri. Non mostra separatamente i sotto-registri, come %ax . |
info registers | i r | i r reg | Mostra lo stato del registro reg specificato. reg va specificato in minuscolo senza caratteri preposti, per esempio i r eax . Si possono specificare anche sotto-registri, come %ax , e più registri separati da spazio. |
gdb
supporta viste alternative con il comando layout
che mettono più informazioni a schermo.
In particolare, layout regs
mostra l'equivalente di i r
e l
, evidenziando gli elementi che cambiano ad ogni step di esecuzione.
Ispezione della memoria
Nome completo | Nome scorciatoia | Formato | Comportamento |
---|---|---|---|
x | x | x/NFU addr | Mostra lo stato della memoria a partire dall'indirizzo addr, per le N locazione di dimensione U e interpretate con il formato F. Comando con memoria, i valori di N, F e U possono essere omessi (insieme allo / ) se uguali a prima. |
Il comando x
sta per examine memory, ma differenza degli altri non ha una versione estesa.
Il parametro N si specifica come un numero intero, il valore di default (all'avvio di gdb
) è 1.
Il parametro F può essere
x
per esadecimaled
per decimalec
per ASCIIt
per binarios
per stringa delimitata da0x00
Il valore di default (all'avvio di gdb
) è x
.
Il parametro U può essere
b
per byteh
per word (2 byte)w
per long (4 byte)
Il valore di default (all'avvio di gdb
) è h
.
L'argomento addr può essere espresso in diversi modi, sia usando label che registri o espressioni basate su aritmetica dei puntatori. Per esempio:
- letterale esadecimale:
x 0x56559066
- label:
x &label
- registro puntatore:
x $esi
- registro puntatore e registro indice:
x (char*)$esi + $ecx
Notare che nell'ultimo caso, dato che ci si basa su aritmetica dei puntatori, il tipo all'interno del cast determina la scala, ossia la dimensione di ciascuna delle $ecx
locazioni del vettore da saltare.
Si può usare (char*)
per 1 byte, (short*)
per 2 byte, (int*)
per 4 byte.
Un alternativa a questo è lo scomporre, anche solo temporaneamente, le istruzioni con indirizzamento complesso. Per esempio, si può sostituire movb (%esi, %ecx), %al
con lea (%esi, %ecx), %ebx
seguita da movb (%ebx), %al
, così che si possa eseguire semplicemente x $ebx
nel debugger.
Gestione dei breakpoints
Oltre a crearli, i breakpoint possono anche essere rimossi o (dis)abilitati.
Questi comandi si basano sulla conoscenza dell'id di un breakpoint: questo viene stampato quando un breakpoint viene creato o raggiunto durante l'esecuzione,oppure si possono ristampare tutti usando info b
.
Nome completo | Nome scorciatoia | Formato | Comportamento |
---|---|---|---|
info breakpoints | info b | info b [id] | Stampa informazioni sul breakpoint id, o tutti se l'argomento è omesso. |
disable breakpoints | dis | dis [id] | Disabilita il breakpoint id, o tutti se l'argomento è omesso. |
enable breakpoints | en | en [id] | Abilita il breakpoint id, o tutti se l'argomento è omesso. |
delete breakpoints | d | d [id] | Rimuove il breakpoint id, o tutti se l'argomento è omesso. |
Conditional Breakpoints
In alcuni casi, la complessità del programma, l'uso intensivo di sottoprogrammi o lunghi loop possono rendere molto lungo trovare il punto giusto dell'esecuzione. A questo scopo, è possibile definire dei breakpoint condizionali, per far sì che l'esecuzione si interrompa a tale breakpoint solo se la condiziona è verificata.
Nome completo | Nome scorciatoia | Formato | Comportamento |
---|---|---|---|
condition | cond | cond id cond | Imposta la condizione cond per il breakpoint id. |
La sintassi per una condizione è in "stile C", come il comando x
.
Alcuni esempi di questa sintassi:
cond 2 $al==5
per far sì che l'esecuzione si fermi al breakpoint 2 solo se il registroal
contiene il valore 5,cond 2 (short *)$edi==-5
per far sì che l'esecuzione si fermi al breakpoint 2 solo se il registroedi
contiene l'indirizzo di una word di valore -5,cond 2 (int *)&count!=0
per far sì che l'esecuzione si fermi al breakpoint 2 solo se la locazione di 4 byte a partire dacount
contiene un valore diverso da 0,
Fare attenzione alle conversioni automatiche di rappresentazione: quando si usa la rappresentazione decimale, gdb
interpreta automaticamente i valori come interi.
Una condizione come cond 2 $al==128
, per quanto accettata dal debugger, sarà sempre falsa perché la codifica 0x80
è interpretata in decimale come l'intero -128
, mai come il naturale 128
.
È quindi una buona idea usare la notazione esadecimale in casi del genere, cioè quanto il bit più significativo è 1.
Una feature disponibile in molti IDE è quello di creare dipendenze tra breakpoint, cioè abilitare un breakpoint solo se è stato prima colpito un altro.
Questo però è fin troppo ostico da fare in gdb
.
Watchpoints
I watchpoint sono come dei breapoint ma per dati (registri e memoria), non per il codice. Si creano indicando l'espressione del dato da controllare. Si gestiscono con gli stessi comandi per i breakpoint.
Nome completo | Nome scorciatoia | Formato | Comportamento |
---|---|---|---|
watchpoint | watch | watch expr | Imposta un watchpoint per l'espressione expr. |
info watchpoints | info wat | info wat [id] | Stampa informazioni sul watchpoint id, o tutti se l'argomento è omesso. |
disable breakpoints | dis | dis [id] | Disabilita il breakpoint o watchpoint id, o tutti se l'argomento è omesso. |
enable breakpoints | en | en [id] | Abilita il breakpoint o watchpoint id, o tutti se l'argomento è omesso. |
delete breakpoints | d | d [id] | Rimuove il breakpoint o watchpoint id, o tutti se l'argomento è omesso. |
Un watchpoint richiede la specifica di un registro o locazione nella stessa notazione "stile C" del comando x
, e interrompe l'esecuzione quando tale valore cambia.
Per esempio, watch $eax
crea un watchpoint che interrompe l'esecuzione ogni volta che eax
cambia valore.